// addrspace.cc 
//	Routines to manage address spaces (executing user programs).
//
//	In order to run a user program, you must:
//
//	1. link with the -N -T 0 option 
//	2. run coff2noff to convert the object file to Nachos format
//		(Nachos object code format is essentially just a simpler
//		version of the UNIX executable object code format)
//	3. load the NOFF file into the Nachos file system
//		(if you haven't implemented the file system yet, you
//		don't need to do this last step)
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "addrspace.h"
#include "bitmap.h"
#include "noff.h"
#include "bitmap.h"
#include "synch.h"
#ifdef HOST_SPARC
#include <strings.h>
#endif

//----------------------------------------------------------------------
// SwapHeader
// 	Do little endian to big endian conversion on the bytes in the 
//	object file header, in case the file was generated on a little
//	endian machine, and we're now running on a big endian machine.
//----------------------------------------------------------------------

extern int memAllocType;
//extern int virtualMemType;
//extern bool printSpecial;
static BitMap* globalMap; 
static void InitBitMap(int size = 32) 
{ 
	globalMap = new BitMap(size); 
}

static void 
SwapHeader (NoffHeader *noffH)
{
	noffH->noffMagic = WordToHost(noffH->noffMagic);
	noffH->code.size = WordToHost(noffH->code.size);
	noffH->code.virtualAddr = WordToHost(noffH->code.virtualAddr);
	noffH->code.inFileAddr = WordToHost(noffH->code.inFileAddr);
	noffH->initData.size = WordToHost(noffH->initData.size);
	noffH->initData.virtualAddr = WordToHost(noffH->initData.virtualAddr);
	noffH->initData.inFileAddr = WordToHost(noffH->initData.inFileAddr);
	noffH->uninitData.size = WordToHost(noffH->uninitData.size);
	noffH->uninitData.virtualAddr = WordToHost(noffH->uninitData.virtualAddr);
	noffH->uninitData.inFileAddr = WordToHost(noffH->uninitData.inFileAddr);
}

//----------------------------------------------------------------------
// AddrSpace::AddrSpace
// 	Create an address space to run a user program.
//	Load the program from a file "executable", and set everything
//	up so that we can start executing user instructions.
//
//	Assumes that the object code file is in NOFF format.
//
//	First, set up the translation from program memory to physical 
//	memory.  For now, this is really simple (1:1), since we are
//	only uniprogramming, and we have a single unsegmented page table
//
//	"executable" is the file containing the object code to load into memory
//----------------------------------------------------------------------

void PrintExecutable(OpenFile* exe)
{
    NoffHeader noffH;
        exe->ReadAt((char *)&noffH, sizeof(noffH), 0);
        
           int size = noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStackSize;
    // we need to increase the size to leave room for the stack
    int numPages = divRoundUp(size, PageSize);
    size = numPages * PageSize;
    
    char fileContents[size];
    exe->ReadAt(&(fileContents[0]), size, 0);
	/*
	printf("\n0------------header----------------%d\n", sizeof(noffH));   
    for (int i = 0; i < sizeof(noffH); i++)
    {
    	printf("%d", fileContents[i]);
    }
    */
    printf("\n%d-------------code-----------------%d\n", noffH.code.inFileAddr, noffH.code.inFileAddr + noffH.code.size);   
    for (int i = noffH.code.inFileAddr; i < noffH.code.inFileAddr + noffH.code.size; i++)
    {
    	printf("%d", fileContents[i]);
    }

	printf("\n");
    
    printf("\n%d-------------data-----------------%d\n", noffH.initData.inFileAddr, noffH.initData.inFileAddr + noffH.initData.size);   
    for (int i = noffH.initData.inFileAddr; i < noffH.initData.inFileAddr + noffH.initData.size; i++)
    {
    	printf("%d", fileContents[i]);
    }

	printf("\n");
    /*
      printf("\n%d-----------uninitdata---------------%d\n", noffH.uninitData.inFileAddr, noffH.uninitData.inFileAddr + noffH.uninitData.size);   
    for (int i = noffH.uninitData.inFileAddr; i < noffH.uninitData.inFileAddr + noffH.uninitData.size; i++)
    {
    	printf("%d", fileContents[i]);
    }
    
   */
}

AddrSpace::AddrSpace(OpenFile *executable)
{
	valid = 0;
	
    NoffHeader noffH;
    int size;
    executable->ReadAt((char *)&noffH, sizeof(noffH), 0);
    if ((noffH.noffMagic != NOFFMAGIC) && 
		(WordToHost(noffH.noffMagic) == NOFFMAGIC))
    SwapHeader(&noffH);
    if(noffH.noffMagic != NOFFMAGIC) {
    	printf("Invalid file name.\n");
    	return;
    }

    // how big is address space?
    size = noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStackSize;
    // we need to increase the size to leave room for the stack
    numPages = divRoundUp(size, PageSize);
    size = numPages * PageSize;

    ASSERT(numPages <= NumPhysPages);
    
    exe = executable;
	
    // first, set up the translation 
     
    pageTable = new TranslationEntry[numPages];
    
    for (int i = 0; i < numPages; i++) 
    {
		pageTable[i].virtualPage = i;	// for now, virtual page # = phys page #
		pageTable[i].physicalPage = NULL;
		pageTable[i].valid = FALSE;
		pageTable[i].use = FALSE;
		pageTable[i].dirty = FALSE;
		pageTable[i].readOnly = FALSE;  
		// if the code segment was entirely on a separate page, we could set its pages to be read-only
    }
    
    valid = 1;
}


//----------------------------------------------------------------------
// AddrSpace::~AddrSpace
// 	Dealloate an address space.  Nothing for now!
//----------------------------------------------------------------------

AddrSpace::~AddrSpace()
{
	IntStatus oldLevel = interrupt->SetLevel(IntOff);
	IPT->Clear(currentThread->getThreadID());
	
    char swapFileName[4001];
    sprintf(swapFileName, "%d.swap", currentThread->getThreadID());
    
    if(printSpecial)
        printf("Deleting swap file %s\n", swapFileName);
    
    fileSystem->Remove(swapFileName);
    
    delete swapFile;
	delete exe;
	delete pageTable;
	
	(void) interrupt->SetLevel(oldLevel);
}

void
AddrSpace::CreateSwapFile(int processID)
{
	valid = 0;
	
	NoffHeader noffH;
    exe->ReadAt((char *)&noffH, sizeof(noffH), 0);

	char swapFileName[4001];
	sprintf(swapFileName, "swap_files/%d.swap", processID);

	int size = numPages * PageSize;

    if(printSpecial)
        printf("Creating swap file named %s associated with process %d.\n", swapFileName, processID);
    
	fileSystem->Create(swapFileName, size);
	swapFile = fileSystem->Open(swapFileName);
	
	char fileContents[size];

	memset(&(fileContents[0]), 0, size);	
	exe->ReadAt(&(fileContents[0]), divRoundUp(noffH.code.size + noffH.initData.size, PageSize) * PageSize, sizeof(noffH)); // noffH.code.inFileAddr);
    
    if (printSpecial)
        printf("Writing to swap file %s\n", swapFileName);
    
	swapFile->Write(fileContents, size);
	valid = 1;
}

void
AddrSpace::SetPage(int physicalPage, int virtualAddr)
{
	if (virtualMemType == 1 || virtualMemType == 2)
	{
		SwapIn(physicalPage, virtualAddr);
	}
	
	else
	{
		int virtualPage = virtualAddr / PageSize;

		pageTable[virtualPage].physicalPage = physicalPage; // Assign the physical page number to the local page table.

		NoffHeader noffH;
		exe->ReadAt((char *)&noffH, sizeof(noffH), 0);
		 
	    memset(&(machine->mainMemory[physicalPage * PageSize]), 0, PageSize);
		
		if (virtualAddr < noffH.code.size + noffH.initData.size)
		{
			exe->ReadAt(&(machine->mainMemory[physicalPage * PageSize]), PageSize, virtualPage * PageSize + sizeof(noffH));
		}

	   	pageTable[virtualPage].valid = TRUE;
	   	pageTable[virtualPage].dirty = FALSE;
   	}
}

void
AddrSpace::SwapOut(int physicalPage, int virtualPage)
{
    if (printSpecial)
	   printf("Swaping out physical page %d from process %d.\n", pageTable[virtualPage].physicalPage, IPT->printIPTprocess(physicalPage));
	//printf("SWAP OUT is %d thought %d\n",pageTable[virtualPage].physicalPage, physicalPage);

	swapFile->WriteAt(&(machine->mainMemory[physicalPage * PageSize]), PageSize, virtualPage * PageSize);

	pageTable[virtualPage].valid = FALSE;
	pageTable[virtualPage].physicalPage = NULL;
}
void
AddrSpace::fixshit(int vpn)
{
	pageTable[vpn].valid = TRUE;
	pageTable[vpn].dirty = FALSE;
}

void 
AddrSpace::SwapIn(int physicalPage, int virtualAddr)
{
	int virtualPage = virtualAddr / PageSize;
	
	if (printSpecial)
        printf("Swaping in process %d to physical page %d.\n", currentThread->getThreadID(), physicalPage);
	
    // printf("SWAP IN is %d thought %d\n",pageTable[virtualPage].physicalPage, physicalPage);
	
	memset(&(machine->mainMemory[physicalPage * PageSize]), 0, PageSize);

	swapFile->ReadAt(&(machine->mainMemory[physicalPage * PageSize]), PageSize, virtualPage * PageSize);
	/*
	printf("Virtual page %d \n", virtualPage);
	for (int i = physicalPage * PageSize; i < physicalPage * PageSize + PageSize; i++)
	{
		printf("%d", machine->mainMemory[i]);
	}
	printf("\n");
	*/
	pageTable[virtualPage].physicalPage = physicalPage;

	pageTable[virtualPage].valid = TRUE;
	pageTable[virtualPage].dirty = FALSE;
	
	IPT->Print();
}

//----------------------------------------------------------------------
// AddrSpace::InitRegisters
// 	Set the initial values for the user-level register set.
//
// 	We write these directly into the "machine" registers, so
//	that we can immediately jump to user code.  Note that these
//	will be saved/restored into the currentThread->userRegisters
//	when this thread is context switched out.
//----------------------------------------------------------------------
void
AddrSpace::InitRegisters()
{
    int i;

    for (i = 0; i < NumTotalRegs; i++)
	machine->WriteRegister(i, 0);

    // Initial program counter -- must be location of "Start"
    machine->WriteRegister(PCReg, 0);	

    // Need to also tell MIPS where next instruction is, because
    // of branch delay possibility
    machine->WriteRegister(NextPCReg, 4);

	// Set the stack register to the end of the address space, where we
	// allocated the stack; but subtract off a bit, to make sure we don't accidentally reference off the end!
    machine->WriteRegister(StackReg, numPages * PageSize - 16);
    DEBUG('a', "Initializing stack register to %d\n", numPages * PageSize - 16);
}

//----------------------------------------------------------------------
// AddrSpace::SaveState
// 	On a context switch, save any machine state, specific
//	to this address space, that needs saving.
//
//	For now, nothing!
//----------------------------------------------------------------------

void AddrSpace::SaveState() 
{}

//----------------------------------------------------------------------
// AddrSpace::RestoreState
// 	On a context switch, restore the machine state so that
//	this address space can run.
//
//      For now, tell the machine where to find the page table.
//----------------------------------------------------------------------

void AddrSpace::RestoreState() 
{
    machine->pageTable = pageTable;
    machine->pageTableSize = numPages;
}
